package socks;

import java.io.*;
import java.net.*;

/** 
SOCKS4 Reply/Request message.
 */
class Socks4Message extends ProxyMessage
{

    private byte[] msgBytes;
    private int msgLength;

    /**
     * Server failed reply, cmd command for failed request
     */
    public Socks4Message(int cmd)
    {
        super(cmd, null, 0);
        this.user = null;

        msgLength = 2;
        msgBytes = new byte[2];

        msgBytes[0] = (byte) 0;
        msgBytes[1] = (byte) command;
    }

    /**
     *  Server successfull reply
     */
    public Socks4Message(int cmd, InetAddress ip, int port)
    {
        this(0, cmd, ip, port, null);
    }

    /**
     *  Client request
     */
    public Socks4Message(int cmd, InetAddress ip, int port, String user)
    {
        this(SOCKS_VERSION, cmd, ip, port, user);
    }

    /**
     * Most general constructor
     */
    public Socks4Message(int version, int cmd, InetAddress ip, int port, String user)
    {
        super(cmd, ip, port);
        this.user = user;
        this.version = version;

        msgLength = user == null ? 8 : 9 + user.length();
        msgBytes = new byte[msgLength];

        msgBytes[0] = (byte) version;
        msgBytes[1] = (byte) command;
        msgBytes[2] = (byte) (port >> 8);
        msgBytes[3] = (byte) port;

        byte[] addr;

        if (ip != null)
        {
            addr = ip.getAddress();
        }
        else
        {
            addr = new byte[4];
            addr[0] = addr[1] = addr[2] = addr[3] = 0;
        }
        System.arraycopy(addr, 0, msgBytes, 4, 4);

        if (user != null)
        {
            byte[] buf = user.getBytes();
            System.arraycopy(buf, 0, msgBytes, 8, buf.length);
            msgBytes[msgBytes.length - 1] = 0;
        }
    }

    /**
     *Initialise from the stream
     *If clientMode is true attempts to read a server response
     *otherwise reads a client request
     *see read for more detail
     */
    public Socks4Message(InputStream in, boolean clientMode) throws IOException
    {
        msgBytes = null;
        read(in, clientMode);
    }

    public void read(InputStream in) throws IOException
    {
        read(in, true);
    }

    public void read(InputStream in, boolean clientMode) throws IOException
    {
        DataInputStream d_in = new DataInputStream(in);
        version = d_in.readUnsignedByte();
        command = d_in.readUnsignedByte();
        if (clientMode && command != REPLY_OK)
        {
            String errMsg;
            if (command > REPLY_OK && command < REPLY_BAD_IDENTD)
            {
                errMsg = replyMessage[command - REPLY_OK];
            }
            else
            {
                errMsg = "Unknown Reply Code";
            }
            throw new SocksException(command, errMsg);
        }
        port = d_in.readUnsignedShort();
        byte[] addr = new byte[4];
        d_in.readFully(addr);
        ip = bytes2IP(addr);
        host = ip.getHostName();
        if (!clientMode)
        {
            int b = in.read();
            //Hope there are no idiots with user name bigger than this
            byte[] userBytes = new byte[256];
            int i = 0;
            for (i = 0; i < userBytes.length && b > 0; ++i)
            {
                userBytes[i] = (byte) b;
                b = in.read();
            }
            user = new String(userBytes, 0, i);
        }
    }

    public void write(OutputStream out) throws IOException
    {
        if (msgBytes == null)
        {
            Socks4Message msg = new Socks4Message(version, command, ip, port, user);
            msgBytes = msg.msgBytes;
            msgLength = msg.msgLength;
        }
        out.write(msgBytes);
    }

    //Class methods
    static InetAddress bytes2IP(byte[] addr)
    {
        String s = bytes2IPV4(addr, 0);
        try
        {
            return InetAddress.getByName(s);
        }
        catch (UnknownHostException uh_ex)
        {
            return null;
        }
    }
    //Constants
    static final String[] replyMessage =
    {
        "Request Granted",
        "Request Rejected or Failed",
        "Failed request, can't connect to Identd",
        "Failed request, bad user name"
    };
    static final int SOCKS_VERSION = 4;
    public final static int REQUEST_CONNECT = 1;
    public final static int REQUEST_BIND = 2;
    public final static int REPLY_OK = 90;
    public final static int REPLY_REJECTED = 91;
    public final static int REPLY_NO_CONNECT = 92;
    public final static int REPLY_BAD_IDENTD = 93;
}
